// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "cc/layers/layer_utils.h"

#include "cc/layers/layer_impl.h"
#include "cc/trees/layer_tree_host_common.h"
#include "cc/trees/layer_tree_impl.h"
#include "cc/trees/transform_node.h"
#include "ui/gfx/geometry/box_f.h"

namespace cc {

namespace {

    bool HasTransformAnimationThatInflatesBounds(const LayerImpl& layer)
    {
        return layer.HasTransformAnimationThatInflatesBounds();
    }

    inline bool HasAncestorTransformAnimation(const TransformNode* transform_node)
    {
        return transform_node->to_screen_is_potentially_animated;
    }

    inline bool HasAncestorFilterAnimation(const LayerImpl& layer)
    {
        // This function returns false, it should use the effect tree once filters
        // and filter animations are known by the tree.
        return false;
    }

} // namespace

bool LayerUtils::GetAnimationBounds(const LayerImpl& layer_in, gfx::BoxF* out)
{
    // We don't care about animated bounds for invisible layers.
    if (!layer_in.DrawsContent())
        return false;

    TransformTree& transform_tree = layer_in.layer_tree_impl()->property_trees()->transform_tree;

    // We also don't care for layers that are not animated or a child of an
    // animated layer.
    if (!HasAncestorTransformAnimation(
            transform_tree.Node(layer_in.transform_tree_index()))
        && !HasAncestorFilterAnimation(layer_in))
        return false;

    // To compute the inflated bounds for a layer, we start by taking its bounds
    // and converting it to a 3d box, and then we transform or inflate it
    // repeatedly as we walk up the transform tree to the root.
    //
    // At each transform_node without transform animation that inflates bounds:
    //
    //   1) We concat transform_node->data.to_parent to the coalesced_transform.
    //
    // At each transform_node with a transform animation that inflates bounds:
    //
    //   1) We concat transform_node->data.pre_local to the coalesced_transform.
    //   This is to translate the box so that the anchor point is the origin.
    //   2) We apply coalesced_transform to the 3d box and make the transform
    //   identity. This is apply the accumulated transform to the box to inflate
    //   it.
    //   3) We inflate the 3d box for animation as the node has a animated
    //   transform.
    //   4) We concat transform_node->data.post_local to the coalesced_transform.
    //   This step undoes the translation in step (1), accounts for the layer's
    //   position and the 2d translation to the space of the parent transform
    //   node.

    gfx::BoxF box(layer_in.bounds().width(), layer_in.bounds().height(), 0.f);

    // We want to inflate/transform the box as few times as possible. Each time
    // we do this, we have to make the box axis aligned again, so if we make many
    // small adjustments to the box by transforming it repeatedly rather than
    // once by the product of all these matrices, we will accumulate a bunch of
    // unnecessary inflation because of the the many axis-alignment fixes. This
    // matrix stores said product.
    gfx::Transform coalesced_transform;

    const TransformNode* transform_node = transform_tree.Node(layer_in.transform_tree_index());

    // If layer_in has a transform node, the offset_to_transform_parent() is 0
    coalesced_transform.Translate(layer_in.offset_to_transform_parent().x(),
        layer_in.offset_to_transform_parent().y());

    for (; transform_tree.parent(transform_node);
         transform_node = transform_tree.parent(transform_node)) {
        LayerImpl* layer = layer_in.layer_tree_impl()->LayerById(transform_node->owning_layer_id);

        // Filter animation bounds are unimplemented, see function
        // HasAncestorFilterAnimation() for reference.

        if (HasTransformAnimationThatInflatesBounds(*layer)) {
            coalesced_transform.ConcatTransform(transform_node->pre_local);
            coalesced_transform.TransformBox(&box);
            coalesced_transform.MakeIdentity();

            gfx::BoxF inflated;
            if (!layer->TransformAnimationBoundsForBox(box, &inflated))
                return false;
            box = inflated;

            coalesced_transform.ConcatTransform(transform_node->post_local);
        } else {
            coalesced_transform.ConcatTransform(transform_node->to_parent);
        }
    }

    // If we've got an unapplied coalesced transform at this point, it must still
    // be applied.
    if (!coalesced_transform.IsIdentity())
        coalesced_transform.TransformBox(&box);

    *out = box;

    return true;
}

} // namespace cc
